package com.lzc.aopcache.aspect;

import com.lzc.aopcache.annotation.LzcCacheEvict;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Aspect
@Component
public class LzcCacheEvictAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Around("execution(* com.lzc.aopcache.*.*.*(..))")
    public Object lzcCacheAspect(ProceedingJoinPoint jp) throws Throwable {

        Class<?> cls = jp.getTarget().getClass();
        String methodName = jp.getSignature().getName();

        Map<String, Object> map = isChacheEvict(cls, methodName);
        boolean isCacheEvict = (boolean) map.get("isCacheEvict");

        if (isCacheEvict) {
            String cacheName = (String) map.get("cacheName"); // 缓存名字
            String key = (String) map.get("key"); // 自定义缓存key
            Method method = (Method) map.get("method"); // 方法

            String realCacheName = "";
            // 判断cacheName是否为空，如果cacheName为空则使用默认的cacheName
            if (cacheName.equals("")) {
                realCacheName = cls.getName() + "." + methodName;
            } else {
                realCacheName = cacheName;
            }

            String realKey = "";
            // 判断key是否为空， 如果为空则使用默认的key
            if (key.equals("")) {
                realKey = realCacheName + "::" + defaultKeyGenerator(jp);
            } else {
                realKey = realCacheName + "::" + parseKey(key, method, jp.getArgs());
            }

            // 判断缓存中是否存在该key, 如果存在则直接从缓存中删除该数据
            if (stringRedisTemplate.hasKey(realKey)) {
                stringRedisTemplate.delete(realKey);
            }
        }
        return jp.proceed();
    }

    /**
     * 自定义生成key，使用方法中的参数作为key
     */
    private String defaultKeyGenerator(ProceedingJoinPoint jp) {
        // 获取所有参数的值
        List<String> list = new ArrayList<>();
        Object[] args = jp.getArgs();
        for (Object object : args) {
            list.add(object.toString());
        }
        return list.toString();
    }

    /**
     * 获取缓存的key
     * key 定义在注解上，支持SPEL表达式
     */
    private String parseKey(String key, Method method, Object[] args) {
        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);
        //使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        //把方法参数放入SPEL上下文中
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        return parser.parseExpression(key).getValue(context, String.class);
    }

    private Map<String, Object> isChacheEvict(Class<?> cls, String methodName) {
        boolean isCacheEvict = false;
        Map<String, Object> map = new HashMap<>();
        Method[] methods = cls.getDeclaredMethods();

        for (Method method : methods) {
            if (method.getName().equals(methodName) && method.isAnnotationPresent(LzcCacheEvict.class)) {
                LzcCacheEvict lzcCacheEvict = method.getAnnotation(LzcCacheEvict.class); // 获取方法上的注解
                map.put("cacheName", lzcCacheEvict.cacheName());
                map.put("key", lzcCacheEvict.key());
                map.put("method", method);
                isCacheEvict = true;
                break;
            }
        }
        map.put("isCacheEvict", isCacheEvict);
        return map;
    }
}
